package art.yulang.framework.servlet;

import art.yulang.framework.annotations.*;
import art.yulang.framework.pojo.Handler;
import art.yulang.framework.utils.StringUtil;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class YuDispatcherServlet extends HttpServlet {
    private Properties properties = new Properties();
    private List<String> classNames = new ArrayList<>();
    /**
     * ioc 容器
     */
    private Map<String, Object> ioc = new HashMap<>();

    /**
     * 映射器
     */
//    private Map<String, Method> handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件 yumvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        //2、扫描相关的类，相关注解
        doScan(properties.getProperty("scanPackage"));

        //3、初始化bean对象（实现ioc容器，基于注解）
        doInstance();

        //4、实现依赖注入
        doAutowired();

        //5、构造一个HandlerMapping处理器映射器，将配置好的url和method建立映射关系
        initHandlerMapping();

        System.out.println("Yu mvc 初始化完成...");
    }

    /**
     * 构造处理器映射器
     */
    private void initHandlerMapping() {
        if(ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()){
            Class<?> aClass = entry.getValue().getClass();
            if(!aClass.isAnnotationPresent(YuController.class)) continue;

            /**
             * Controller上是否有@YuSecurity注解，有取注解的value存放在auth数组待后续处理
             */
            String[] auth = {};
            if (aClass.isAnnotationPresent(YuSecurity.class)){
                YuSecurity annotation = aClass.getAnnotation(YuSecurity.class);
                auth = annotation.value();
            }

            String baseUrl = "";
            if (aClass.isAnnotationPresent(YuRequestMapping.class)){
                YuRequestMapping annotation = aClass.getAnnotation(YuRequestMapping.class);
                baseUrl = annotation.value();
            }
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(YuRequestMapping.class)) continue;
                String value = method.getAnnotation(YuRequestMapping.class).value();

                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(baseUrl + value));
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class){
                        handler.getParameterIndexMapping().put(parameter.getType().getSimpleName(), i);
                    }else{
                        handler.getParameterIndexMapping().put(parameter.getName(), i);
                    }
                }

                /**
                 * 是否有@YuSecurity注解，有将注解value放入handler中方便后续doPost请求时使用
                 */
                if(method.isAnnotationPresent(YuSecurity.class)){
                    YuSecurity annotation = method.getAnnotation(YuSecurity.class);
                    if(auth.length == 0){
                        auth = annotation.value();
                    }else{
                        auth = arrayMerge(auth, annotation.value());
                    }
                    handler.setAuth(auth);
                }

                handlerMapping.add(handler);
            }
        }
    }

    /**
     * 依赖注入
     */
    private void doAutowired() {
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()){
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if(declaredField.isAnnotationPresent(YuAutowired.class)){
                    YuAutowired annotation = declaredField.getAnnotation(YuAutowired.class);
                    String beanName = annotation.value();
                    if("".equals(beanName.trim())){
                        beanName = declaredField.getType().getSimpleName();
                    }
                    declaredField.setAccessible(true);
                    try {
                        declaredField.set(entry.getValue(), ioc.get(beanName));
                        declaredField.setAccessible(false);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }else{
                    continue;
                }
            }
        }
    }

    /**
     * ioc容器
     */
    private void doInstance() {
        if(classNames.size() == 0) return;
        try {
            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(YuController.class)){
                    String lowerFirstSimpleName = StringUtil.lowerFirst(aClass.getSimpleName());
                    ioc.put(lowerFirstSimpleName, aClass.newInstance());
                }else if(aClass.isAnnotationPresent(YuService.class)){
                    YuService annotation = aClass.getAnnotation(YuService.class);
                    String key = annotation.value();
                    if (!"".equals(key.trim())){
                        ioc.put(key, aClass.newInstance());
                    }else{
                        ioc.put(StringUtil.lowerFirst(aClass.getSimpleName()), aClass.newInstance());
                    }

                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        ioc.put(anInterface.getSimpleName(), aClass.newInstance());
                    }
                }else{
                    continue;
                }
            }
        }catch (Exception exception){
            exception.printStackTrace();
        }
    }

    /**
     * 扫描相关类
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
        String scanPath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if(file.isDirectory()){
                doScan(scanPackage + "." + file.getName());
            }else if(file.getName().endsWith(".class")){
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * 加载配置文件
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Handler handler = getHandler(req);
        if(handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }
        Map<String, String[]> parameterMap = req.getParameterMap();
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        Object[] args = new Object[parameterTypes.length];

        String username = "";
        for(Map.Entry<String,String[]> param: parameterMap.entrySet()) {
            if("username".equals(param.getKey())){
                username = StringUtils.join(param.getValue(), ",");
            }

            // name=1&name=2   name [1,2]
            String value = "";//StringUtils.join(param.getValue(), ",");  // 如同 1,2
            value = StringUtils.join(param.getValue(), ",");
            // 如果参数和方法中的参数匹配上了，填充数据
            if(!handler.getParameterIndexMapping().containsKey(param.getKey())) {continue;}

            // 方法形参确实有该参数，找到它的索引位置，对应的把参数值放入paraValues
            Integer index = handler.getParameterIndexMapping().get(param.getKey());//name在第 2 个位置

            args[index] = value;  // 把前台传递过来的参数值填充到对应的位置去
        }

        if(!isHasAuth(handler, username)){
            resp.getWriter().write("auth failed");
            return;
        }

        int requestIndex = handler.getParameterIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0
        if(requestIndex >= 0){
            args[requestIndex] = req;
        }

        int responseIndex = handler.getParameterIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1
        if(responseIndex >= 0){
            args[responseIndex] = resp;
        }

        try {
            Object invoke = handler.getMethod().invoke(handler.getController(), args);
            resp.getWriter().write(invoke.toString());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) return null;
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(req.getRequestURI());
            if(!matcher.matches()) continue;
            return handler;
        }
        return null;
    }

    /**
     * 判断是否有访问权限
     * @param handler handler
     * @param username
     * @return true：有访问权限；false：无访问权限
     */
    private Boolean isHasAuth(Handler handler, String username){
        Boolean flag = false;

        String[] auth = handler.getAuth();
        if(null == auth || auth.length == 0){
            return !flag;
        }

        if(StringUtils.isEmpty(username)){
            return flag;
        }

        for (String s : auth) {
            if(s.equals(username)){
                flag = true;
                break;
            }
        }
        return flag;
    }

    /**
     * 字符串数组合并
     * @param auth
     * @param value
     * @return
     */
    private String[] arrayMerge(String[] auth, String[] value) {
        String[] array = new String[auth.length+value.length];
        String str1 = StringUtils.join(auth, ",");
        String str2 = StringUtils.join(value, ",");
        String str = str1 + "," + str2;
        array = str.split(",");
        return array;
    }
}
